package a5.common;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;


/**
    Instances of this class represent tokens appropriate
      for use in a recursive descent parser as implemented
      by the Parser class of this package (using the grammar
      recognized by that class).

   @author Jeff Smith w/ minor modifications and additions by Jason Campos
   @version for CS 152, Fall 2012, SJSU
 */

public class Token
{

    // tokens have a type name (e.g., "StringLiteral")
    //   and a spelling (e.g., "xyz")
    private String type;
    private String spelling;
    public static final String END_OF_INPUT_TYPE = "end-of-input";

    // the set of permitted singleton types
    public static final String[] singletons =
      {"(",")","+","-","begin","body","end","find","with"};

    public Token()                  {
      this(END_OF_INPUT_TYPE);      }

    /**
         Constructs a token whose type equals its spelling
         @param v the type and spelling
     */

    public Token(String v)
    {
        type = v;
        spelling = v;
    }


    /**
         Constructs a token given a type and a spelling
         @param t the type
         @param v the spelling
      */

    public Token(String t, String v)
    {
        type = t;
        spelling = v;
    }


    /**
        Finds the type of the token
        @return the type of the token
     */

    public String getType()      {
        return type;             }


    /**
        Finds the value of the token
        @return the value of the token
     */

    public String getSpelling()   {
        return spelling;          }


    /**
        Finds a string representing the token
        @return the value of the token
     */

    public String toString()      {
      return type + ":" + spelling;            }


    /**
        Determines whether a string is the spelling of a
          legal identifier
        @return <code>true</code> iff it is
     */

    public static boolean isVariableName(String s)      {
      if (s == null || s.equals(""))
        return false;
      char firstChar = s.charAt(0);
      return Character.isUpperCase(firstChar);        }


    public static boolean isUserFunctionName(String s)  {
      if (s == null || s.equals(""))
        return false;
      char firstChar = s.charAt(0);
      return Character.isLowerCase(firstChar);          }


    /**
        Determines whether a string is the spelling of a
          legal string literal
        @param the string to test
        @return <code>true</code> iff the string is the
          spelling of a legal string literal
     */

    public static boolean isIntegerLiteral(String s)   {
      try                                           {
        Integer.valueOf(s);
        return true;                                }
      catch(NumberFormatException e)                {
        return false;                               }  }


    /**
        Finds the type (name) of a token with a given spelling
        @return the appropriate type name
        @throws IllegalArgumentException if no token exists with
           the given spelling
     */

    public static String getTokenType(String spelling)         {
      if (Arrays.binarySearch(singletons, spelling) >= 0)
        return spelling;
      if (isVariableName(spelling))
        return "VariableName";
      if (isUserFunctionName(spelling))
        return "UserFunctionName";
      if (isIntegerLiteral(spelling))
        return "IntegerLiteral";
      else
        throw new IllegalArgumentException("illegal token: " + spelling);   }


    /**
         Converts a text representing a program into a list of tokens
         @return the list of tokens, with a dummy token at the end
           whose spelling and token type are both "end-of-input".
         @throws IllegalArgumentException if the input is null
           or if some substring delimited by white space
           fails to be the spelling of a legal token
     */
    public static LinkedList<Token> tokenize(String program)          {
      if (program == null)
        throw new IllegalArgumentException(
          "input string cannot be null");
      Scanner sc = new Scanner(program);
      LinkedList<Token> output = new LinkedList<Token>();
      while (sc.hasNext())                              {
        String s = sc.next();
        output.add(new Token(getTokenType(s), s));      }
      sc.close();
      return output;                                            }
    
    /**
     * Converts the argument {@code List<String>} into a {@code List<Token>}.
     * @param words
     * The words to tokenize
     * @return
     * A {@code List<Token>} generated by creating a new token for each value in the argument
     */
    public static LinkedList<Token> tokenize(LinkedList<String> words){
    	if(words == null)
    		throw new IllegalArgumentException("input string collection cannot be null");
    	
    	LinkedList<Token> output = new LinkedList<>();
    	for(String s: words)
    		output.add(new Token(getTokenType(s), s));
    	
    	return output;
    }
    
    /**
     * Asserts that the argument token is of the type expectedType.
     * @param token
     * The token on which to perform the assertion
     * @param expectedType
     * The expected type of the argument token
     * @throws IllegalArgumentException
     * Thrown when the argument token is not of type expectedType
     */
    public static void assertType(Token token, String expectedType) throws IllegalArgumentException {
    	if(!token.getType().equals(expectedType))
    		throw new IllegalArgumentException(token.spelling + " found, " + expectedType + " expected");
    }
}
